using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using NUnit.Framework;
using NUnit.Extensions.Forms;

namespace DigitalTrainSuite.Canvas
{
    [TestFixture]
	public class EndPoint
	{

		public static List<DoublePoint> findEndPoints(DataSetLayout.TrackRow track)
		{
			List<DoublePoint> points = new List<DoublePoint>();
			double x = track.locationX;
			double y = track.locationY;
			DoublePoint point;
			points.Add(new DoublePoint(track.locationX, track.locationY));

			switch ((TrackType)track.type)
			{
				case TrackType.Straight:
				case TrackType.Straight775:
				case TrackType.Straight94:
				case TrackType.Straight172:
				case TrackType.Straight188:
					points.Add(new DoublePoint(x + track.length * Math.Sin(track.rotation * Math.PI / 180), y - track.length * Math.Cos(track.rotation * Math.PI / 180)));
					break;
				case TrackType.CurveRight:
				case TrackType.CurveRightR1:
				case TrackType.CurveRightR2:
				case TrackType.CurveRightR3:
					points.Add(EndPoint.rightCurveEndPoint(x, y, track.radius, track.angle, track.rotation));
					break;
				case TrackType.CurveLeft:
				case TrackType.CurveLeftR1:
				case TrackType.CurveLeftR2:
				case TrackType.CurveLeftR3:
					points.Add(EndPoint.leftCurveEndPoint(x, y, track.radius, track.angle, track.rotation));
					break;
				case TrackType.TurnoutStraightRight:
					points.Add(new DoublePoint(x + 188 * Math.Sin(track.rotation * Math.PI / 180), y - 188 * Math.Cos(track.rotation * Math.PI / 180)));
					points.Add(EndPoint.rightCurveEndPoint(track.locationX, track.locationY, 437.5, 24.3, track.rotation));
					break;
				case TrackType.TurnoutStraightLeft:
					points.Add(new DoublePoint(x + 188 * Math.Sin(track.rotation * Math.PI / 180), y - 188 * Math.Cos(track.rotation * Math.PI / 180)));
					points.Add(EndPoint.leftCurveEndPoint(track.locationX, track.locationY, 437.5, 24.3, track.rotation));
					break;
				case TrackType.TurnoutCurveRight:
					point = EndPoint.rightCurveEndPoint(track.locationX, track.locationY, 437.5, 24.3, track.rotation);
					points.Add(new DoublePoint(point.X + 77.5 * Math.Sin(track.rotation * Math.PI / 180), point.Y - 77.5 * Math.Cos(track.rotation * Math.PI / 180)));
					points.Add(point);
					break;
				case TrackType.TurnoutCurveLeft:
					point = EndPoint.leftCurveEndPoint(track.locationX, track.locationY, 437.5, 24.3, track.rotation);
					points.Add(new DoublePoint(point.X + 77.5 * Math.Sin(track.rotation * Math.PI / 180), point.Y - 77.5 * Math.Cos(track.rotation * Math.PI / 180)));
					points.Add(point);
					break;
				case TrackType.DoubleSlipSwitch:
					DoublePoint midpoint = new DoublePoint(track.locationX + 94 * Math.Sin(track.rotation), track.locationY - 94 * Math.Cos(track.rotation));
					DoublePoint e1 = new DoublePoint(midpoint.X - 94 * Math.Sin(track.rotation), midpoint.Y + 94 * Math.Cos(track.rotation));
					points.Add(new DoublePoint(x + 188 * Math.Sin(track.rotation * Math.PI / 180), y - 188 * Math.Cos(track.rotation * Math.PI / 180)));
					points.Add(new DoublePoint(e1.X + 188 * Math.Sin(track.rotation * Math.PI / 180), e1.Y - 188 * Math.Cos(track.rotation * Math.PI / 180)));
					points.Add(e1);
					break;
				default:
					break;
			}

			return points;
		}

		public static List<DoublePoint> findMidPoints(DataSetLayout.TrackRow track)
		{
			List<DoublePoint> points = new List<DoublePoint>();
			double x = track.locationX;
			double y = track.locationY;
			DoublePoint point;

			switch ((TrackType)track.type)
			{
				case TrackType.Straight:
				case TrackType.Straight775:
				case TrackType.Straight94:
				case TrackType.Straight172:
				case TrackType.Straight188:
					for (double deltal = 0; deltal < track.length; deltal += 5.0)
					{
						points.Add(new DoublePoint(x + deltal * Math.Sin(track.rotation * Math.PI / 180), y - deltal * Math.Cos(track.rotation * Math.PI / 180)));
					}
					break;
				case TrackType.CurveRight:
				case TrackType.CurveRightR1:
				case TrackType.CurveRightR2:
				case TrackType.CurveRightR3:
					for (double deltaa = 0; deltaa < track.angle; deltaa += 1.0)
					{
						points.Add(EndPoint.rightCurveEndPoint(x, y, track.radius, deltaa, track.rotation));
					}
					break;
				case TrackType.CurveLeft:
				case TrackType.CurveLeftR1:
				case TrackType.CurveLeftR2:
				case TrackType.CurveLeftR3:
					for (double deltaa = 0; deltaa < track.angle; deltaa += 1.0)
					{
						points.Add(EndPoint.leftCurveEndPoint(x, y, track.radius, deltaa, track.rotation));
					}
					break;
				case TrackType.TurnoutStraightRight:
					for (double deltal = 0; deltal < 188; deltal += 5.0)
					{
						points.Add(new DoublePoint(x + deltal * Math.Sin(track.rotation * Math.PI / 180), y - deltal * Math.Cos(track.rotation * Math.PI / 180)));
					}
					for (double deltaa = 0; deltaa < 24.3; deltaa += 1.0)
					{
						points.Add(EndPoint.rightCurveEndPoint(track.locationX, track.locationY, 437.5, deltaa, track.rotation));
					}
					break;
				case TrackType.TurnoutStraightLeft:
					for (double deltal = 0; deltal < 188; deltal += 5.0)
					{
						points.Add(new DoublePoint(x + deltal * Math.Sin(track.rotation * Math.PI / 180), y - deltal * Math.Cos(track.rotation * Math.PI / 180)));
					}
					for (double deltaa = 0; deltaa < 24.3; deltaa += 1.0)
					{
						points.Add(EndPoint.leftCurveEndPoint(track.locationX, track.locationY, 437.5, deltaa, track.rotation));
					}
					break;
				case TrackType.TurnoutCurveRight:
					for (double deltaa = 0; deltaa < 30; deltaa += 1.0)
					{
						points.Add(EndPoint.rightCurveEndPoint(track.locationX, track.locationY, 360, deltaa, track.rotation));
					}
					for (double deltal = 0; deltal < 77.5; deltal += 5.0)
					{
						points.Add(new DoublePoint(x + deltal * Math.Sin(track.rotation * Math.PI / 180), y - deltal * Math.Cos(track.rotation * Math.PI / 180)));
					}
					point = new DoublePoint(x + 77.5 * Math.Sin(track.rotation * Math.PI / 180), y - 77.5 * Math.Cos(track.rotation * Math.PI / 180));
					for (double deltaa = 0; deltaa < 30; deltaa += 1.0)
					{
						points.Add(EndPoint.rightCurveEndPoint(point.X, point.Y, 360, deltaa, track.rotation));
					}
					break;
				case TrackType.TurnoutCurveLeft:
					for (double deltaa = 0; deltaa < 30; deltaa += 1.0)
					{
						points.Add(EndPoint.leftCurveEndPoint(track.locationX, track.locationY, 360, deltaa, track.rotation));
					}
					for (double deltal = 0; deltal < 77.5; deltal += 5.0)
					{
						points.Add(new DoublePoint(x + deltal * Math.Sin(track.rotation * Math.PI / 180), y - deltal * Math.Cos(track.rotation * Math.PI / 180)));
					}
					point = new DoublePoint(x + 77.5 * Math.Sin(track.rotation * Math.PI / 180), y - 77.5 * Math.Cos(track.rotation * Math.PI / 180));
					for (double deltaa = 0; deltaa < 30; deltaa += 1.0)
					{
						points.Add(EndPoint.leftCurveEndPoint(point.X, point.Y, 360, deltaa, track.rotation));
					}
					break;
				case TrackType.DoubleSlipSwitch:
					DoublePoint midpoint = new DoublePoint(track.locationX + 94 * Math.Sin(track.rotation), track.locationY - 94 * Math.Cos(track.rotation));
					DoublePoint e1 = new DoublePoint(midpoint.X - 94 * Math.Sin(track.rotation), midpoint.Y + 94 * Math.Cos(track.rotation));
					for (double deltal = 0; deltal < 188; deltal += 5.0)
					{
						points.Add(new DoublePoint(x + deltal * Math.Sin(track.rotation * Math.PI / 180), y - deltal * Math.Cos(track.rotation * Math.PI / 180)));
					}
					for (double deltal = 0; deltal < 188; deltal += 5.0)
					{
						points.Add(new DoublePoint(e1.X + deltal * Math.Sin(track.rotation * Math.PI / 180), e1.Y - deltal * Math.Cos(track.rotation * Math.PI / 180)));
					}
					break;
				default:
					break;
			}

			return points;
		}

		public static DoublePoint rightCurveEndPoint(double x, double y, double r, double theta, double phi)
		{
			double a = r * (1 - Math.Cos(theta * Math.PI/180));
			double b = r * Math.Sin(theta * Math.PI / 180);

			if (phi != 0)
			{
				double c = Math.Sqrt(a * a + b * b);
				return new DoublePoint(x + c * Math.Sin(phi * Math.PI / 180 + Math.Asin(a / c)), y - c * Math.Cos(phi * Math.PI / 180 + Math.Acos(b / c)));
			}
			else
			{
				return new DoublePoint(x + a, y - b);
			}
		}

		public static DoublePoint leftCurveEndPoint(double x, double y, double r, double theta, double phi)
		{
			double a = r * (1 - Math.Cos(theta * Math.PI / 180));
			double b = r * Math.Sin(theta * Math.PI / 180);

			if (phi != 0)
			{
				double c = Math.Sqrt(a * a + b * b);
				return new DoublePoint(x - c * Math.Sin(phi * Math.PI / 180 + Math.Asin(a / c)), y - c * Math.Cos(phi * Math.PI / 180 + Math.Acos(b / c)));
			}
			else
			{
				return new DoublePoint(x - a, y - b);
			}
		}

        #region NUnitForms Endpoint Tests

        /// <summary>
        /// Test function to test the correctness of the endpoints of a right Curve
        /// </summary>
        [Test]
        public void Test02_CheckEndpoints_RightCurve()
        {
            DataSetLayout data = new DataSetLayout();
            DataSetLayout.TrackRow track = data.Track.NewTrackRow();
            
            EndPoint testEndpoint = new EndPoint();
            List<DoublePoint> endpointList;
            DoublePoint calcPoint;
           
            track.type = TrackType.CurveRightR1.GetHashCode();
            track.locationX = 0;
            track.locationY = 0;
            track.radius = 360;
            track.angle = 30;
            track.rotation = 0;

            endpointList = EndPoint.findEndPoints(track);
            calcPoint = EndPoint.rightCurveEndPoint(0, 0, 360, 30, 0);
            foreach (DoublePoint point in endpointList)
            {
                Assert.IsTrue(((point.X == track.locationX) || (point.X == calcPoint.X)), "End Point does not match calculated X value for a R1 left curve");
                Assert.IsTrue(((point.Y == track.locationY) || (point.Y == calcPoint.Y)), "End point does not match calculated Y value for a R1 left curve");
            }
        }

        /// <summary>
        /// Test function to test the correctness of the endpoints of a left curve
        /// </summary>
        [Test]
        public void Test02_CheckEndpoints_LeftCurve()
        {
            DataSetLayout data = new DataSetLayout();
            DataSetLayout.TrackRow track = data.Track.NewTrackRow();

            EndPoint testEndpoint = new EndPoint();
            List<DoublePoint> endpointList;
            DoublePoint calcPoint;
            //Test R1 left curve
            track.type = TrackType.CurveLeftR1.GetHashCode();
            track.locationX = 0;
            track.locationY = 0;
            track.radius = 360;
            track.angle = 30;
            track.rotation = 0;

            endpointList = EndPoint.findEndPoints(track);
            calcPoint = EndPoint.leftCurveEndPoint(0, 0, 360, 30, 0);
            foreach (DoublePoint point in endpointList)
            {
                Assert.IsTrue(((point.X == track.locationX) || (point.X == calcPoint.X)), "End Point does not match calculated X value for a R1 left curve");
                Assert.IsTrue(((point.Y == track.locationY) || (point.Y == calcPoint.Y)), "End point does not match calculated Y value for a R1 left curve");
            }
        }

        /// <summary>
        /// Test function to test the correctness of the endpoints of a straight track
        /// </summary>
        [Test]
        public void Test02_CheckEndpoints_Straight()
        {
            DataSetLayout data = new DataSetLayout();
            DataSetLayout.TrackRow track = data.Track.NewTrackRow();

            EndPoint testEndpoint = new EndPoint();
            List<DoublePoint> endpointList;
            //Test the straight 
            track.type = TrackType.Straight188.GetHashCode();
            track.locationX = 0;
            track.locationY = 0;
            track.length = 188;
            track.rotation = 0;
            endpointList = EndPoint.findEndPoints(track);

            foreach (DoublePoint point in endpointList)
            {
                Assert.AreEqual(point.X, 0, "The X value of the endpoint does not match the calculated value");
                Assert.IsTrue(((point.Y == 0) || (point.Y == -188)), "The Y value of the endpoint does not match the calculated value for a straight");
            }
        }

        #endregion
	}
}
